//
//  ForkingServer.m
//  SchmoozingExamples
//
//  Created by garrison on Fri Apr 20 2001.
//  Copyright (c) 2001 Standard Orbit Software, LLC. All rights reserved.
//
//  Permission is granted to use this code for any purpose, at your own risk.
//  No warranties are expressed or implied.
//

/* An Algorithm for a Concurrent Server [Comer93]

Master 1) Create a socket for the server and bind it to the well known port 
          for the service being offered.
Master 2) Establishing a listener on the socket.
Master 3) Repeatedly accept incoming requests and create new slaves to handle the response.
Slave 1) Receive a connection request/socket upon creation.
Slave 2) Interact with the client using the socket, reading requests and sending back replies.
Slave 3) Close the connection and exit.  The slave exits after handling all requests 
         from one client.
*/

#import <OmniNetworking/OmniNetworking.h>
#import <Connection.h>
#import "ForkingServer.h"
#include <unistd.h> // for fork()
#include <sys/wait.h> // for waitpid()

static void reaper();

@implementation ForkingServer

- (id) initWithLocalPort:(unsigned short) aPort
{
    self = [super init];
    
    if ( self )
    {
        NS_DURING {
            
            // Establish a handler for the SIGCHLD signal so that we can
            // do cleanup on forked processes.
            if ( signal( SIGCHLD, reaper ) == SIG_ERR )
            {
                [NSException raise:NSGenericException format:@"can't install SIGCHLD hander (%d) because %s", errno, strerror(errno)];
            }
            
            listeningSocket = [[ONTCPSocket tcpSocket] retain];
            // Master Step 1. Create an ONTCPSocket for the server.
            
            [listeningSocket startListeningOnLocalPort:aPort allowingAddressReuse:YES];
            // Master Step 2.
            // Start socket listening on the well known port.  Allowing address reuse is
            // helpful while debugging (it prevents a port from getting locked up if your
            // app crashes without closing the socket).
            
            NSLog(@"Listening for connections on host %@:%d", [ONHost localHostname], aPort);
        }
        NS_HANDLER {
            // Handle exceptions.
            NSLog(@"Can't start server: %@, %@", [localException name], localException);
            [listeningSocket release];
            return nil;
        }
        NS_ENDHANDLER
    }
        
    return self;
}


- (void) dealloc
{
    [listeningSocket release];
    [super dealloc];
}


- (void) run
{
    // This method implements a concurrent server using forking.  A new copy of the 
    // program is created in a forked process.  The new copy goes on to handle the
    // connection while the original continues on to processing subsequent incoming
    // connections.
    
    ONTCPSocket *connectionSocket = nil;
    Connection *someClient = nil;
    unsigned short connectionCount = 0;
    pid_t forkedPID;
    
    // 'connectionSocket' will hold the accepted connection, leaving 
    // the listening socket free to continue listening.
    
    // 'someClient' is an object of our own custom class for handling
    // connections to our server.
    
    // 'connectionCount' holds a count of connections accepted, just to
    // provide some feedback in the spartan UI.

    NSLog(@"Forking Concurrent Server");
    
    do 
    {
        NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
        // Create an autorelease pool to contain memory usage in the loop.

        NS_DURING 
        {   
            NSLog(@"Parent process listening for next connection");
            
            connectionSocket = [listeningSocket acceptConnectionOnNewSocket];
            // Master Step 3. Accept the next incoming connection.  Server execution
            // will block until a connection request is received.
            
            connectionCount += 1;
            // Increment the accepted connection count

            NSLog(@"Accepted connection %d from host %@", connectionCount, 
                    [[connectionSocket remoteAddressHost] hostname]);
        
            // Fork the process to handle the accepted connection.
            
            forkedPID = fork();
            

            if ( forkedPID == 0 )
            {
                NSLog(@"Child process forked.");
                // We're executing in the child process now.
                // fork() returns a zero value to the newly created child process. 
                // The child process gets a copy of everything the original parent
                // process had (e.g. the accepted connection socket).

                // Continue program execution from this point on by handling
                // the accepted connection.  When finished, close the connection
                // and exit, which will terminate the child process.
                
                [listeningSocket release];
                listeningSocket = nil;
                // Close the child's copy of the listening socket.
                
                someClient = [[Connection alloc] initWithConnectedSocket:connectionSocket];
                // Slave Step 1.  Receive the accepted connection socket on initalization
                // of the slave (the Connection object plays the role of the slave here).
                
                [someClient processConnection];
                // Slave Step 2.  Process the connection in a server-specifc manner.
                                
                [someClient release];
                // Slave Step 3.  Close the connection and exit the slave.
            
                NSLog(@"Child processed connection %d.", connectionCount);

                [loopPool release];
                break;
                // Break out of the processing loop and exit this child process.

            } 
            else if ( forkedPID > 0 )
            {
                NSLog(@"Server spawned child process %d to handle connection", forkedPID );
                // We're still executing in the parent process.  forkedPID holds the
                // process id of the forked child process.  We need to clean up from the
                // for fork and go back to the top of the loop.
                
                [loopPool release];
                connectionSocket = nil;
                // Close the parent's copy of the accepted socket.
                
                continue;
                // Go back to the top of the loop
            }
            else if ( forkedPID == -1 )
            {
                // fork() returned with an error condition.  Clean up and
                // exit.
                
                [loopPool release];
                break;
            } 
        }
        
        NS_HANDLER 
        {
            NSLog(@"Exception %@ raised in forking server loop: %@", 
                    [localException name], localException);
            break;
            // Close done the server loop if an exception occurs.
        }
        NS_ENDHANDLER


    } while (1);
    // Loop forever accepting new connections.  A real server would provide some means
    // of gracefully shutting the server down (e.g. implementing a SIGKILL or SIGHUP
    // handler).
    
    if ( forkedPID == 0 )
        NSLog(@"Child process is exiting");
    // Some informative UI for the console.
    
    exit(0);

}
@end

// reaper() is a cleanup routine intended to be invoked whenever the server
// application receives the SIGCHLD signal.  SIGCHLD is sent to a parent process
// by the OS whenever a forked child process exits.  What we do here is reap any
// incompletely terminated processes ("zombies") to ensure that we don't leave any 
// around to use up system resources.

void reaper()
{
        pid_t reaped;
        int exitStatus; 
        
        do {
            reaped = waitpid( -1, &exitStatus, WNOHANG | WUNTRACED );
        } while ( reaped > 0 );
        // Loop until all terminating child processes have been reaped
        // by waitpid()
}
